package com.gitee.tianchaohongyu.jdcl.core;

import com.gitee.tianchaohongyu.jdcl.util.ReflectUtils;
import org.springframework.lang.Nullable;

import javax.validation.constraints.NotNull;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Objects;

/**
 * 数据清洗服务 默认 实现类
 */
public class CleanServiceDefaultImpl implements CleanService {

  @Override
  @Nullable
  public Object clean(@NotNull String dataName, Object data, @NotNull Class<?> defineType, @NotNull CleanerInfo cleanerInfo, boolean ignoreMismatch) {
    try {

      Class<? extends Cleaner> cleanerType = cleanerInfo.getCleanerClass();
      Class<?> tType = getDataClass(cleanerType);

      //检查清洗器匹配度
      if (tType == null) throw new CleanException(dataName, String.format("无法清洗数据:清洗器(%s) 没有明确的可清洗类型。", cleanerType));

      if (data != null && !ReflectUtils.isAssignableFrom(tType, data.getClass())) {
        //TODO 如果是数组、应进一步处理

        if(ignoreMismatch)  return data;  //如果设置了忽略类型不匹配,则忽略它

        String msg = String.format("无法清洗数据:数据(%s) 必须可赋值到 数据清洗器(%s) 支持的 可清洗类型(%s)。", data, cleanerType, tType);
        throw new CleanException(dataName, msg);
      }

      if (!ReflectUtils.isAssignableFrom(tType, defineType)) {

        if(ignoreMismatch)  return data;  //如果设置了忽略类型不匹配,则忽略它

        String msg = String.format("无法清洗数据:定义类型(%s) 必须可赋值到 数据清洗器(%s) 支持的 可清洗类型(%s)。", defineType, cleanerType, tType);
        throw new CleanException(dataName, msg);
      }

      //开始清洗数据
      Cleaner cleaner;
      try {
        cleaner = cleanerType.newInstance();
      } catch (ReflectiveOperationException e) {
        throw new CleanException(dataName, String.format("构造清洗器(%s)失败:%s", cleanerType, e.getMessage()), e);
      }
      //判断是否需要传入注解
      Class<?> aType = getAnnotationClass(cleanerType);
      Class<? extends Annotation> annotationType = cleanerInfo.getAnnotation().annotationType();
      Annotation annotation = (aType != null && ReflectUtils.isAssignableFrom(aType, annotationType)) ? cleanerInfo.getAnnotation() : null;
      @SuppressWarnings("unchecked")
      Object ret = cleaner.clean(data, defineType, annotation);

      //检查清洗器匹配度
      if (ret != null && !ReflectUtils.isAssignableFrom(defineType, ret.getClass())) {
        String msg = String.format("清洗数据出错:数据清洗器(%s) 返回了错误的类型(%s),它应该可赋值到 定义类型(%s)。", cleaner.getClass(), ret.getClass(), defineType);
        throw new CleanException(dataName, msg);
      }

      //返回清洗结果
      return ret;
    } catch (CleanException e) {
      //如果没有位置信息，则置入位置信息
      if (e.getCodeName() == null) e.setCodeName(dataName);
      throw e;
    } catch (Exception e) {
      //如果不是清洗异常则包装成清洗一次，并置入位置信息
      throw new CleanException(dataName, e.getMessage(), e);
    }
  }

  @Override
  public void cleanFields(@Nullable String objectName, @NotNull Object object) {
    Object targetObj = ReflectUtils.getTargeObject(object);
    Class<?> objType = ReflectUtils.getTargetClass(targetObj);
    if (objType.isPrimitive() || Number.class.isAssignableFrom(objType)) return; //如果是基本类型或数字类型,不检查其属性,直接跳过

    //类上的公共清洗器
    CleanerInfo[] classCleanerInfos = getCleanerInfos(objType.getDeclaredAnnotations());

    //查找属性
    for (Field field : ReflectUtils.getClassFieldList(objType)) {
      Class<?> type = field.getType();
      Object value = ReflectUtils.getFieldValue(targetObj, field);
      Object oldValue = value;
      String fieldName = objectName + "." + field.getName();

      //使用类上标注的清洗器

      for (CleanerInfo classCleanerInfo : classCleanerInfos) {
        value = clean(fieldName, value, type, classCleanerInfo, true);
      }

      //使用属性自有清洗器
      for (CleanerInfo cleanerInfo : getCleanerInfos(field.getDeclaredAnnotations())) {
        value = clean(fieldName, value, type, cleanerInfo, false);
      }
      if (value != oldValue) ReflectUtils.setFieldValue(targetObj, field, value);
    }
  }

  @Override
  public void cleanMethod(@NotNull String methodName, @NotNull Method method, Object[] args) {
    Parameter[] parameters = method.getParameters();
    if (parameters.length != args.length) {
      String msg = String.format("清洗时出错:方法 形参数量(%s) 与 实参数量(%s) 不一致", parameters.length, args.length);
      throw new CleanException(methodName + "()", msg);
    }

    //方法的公共清洗器
    CleanerInfo[] methodCleanerInfos = getCleanerInfos(method.getDeclaredAnnotations());

    //查找参数
    for (int i = 0; i < args.length; i++) {
      Parameter parameter = parameters[i];
      Class<?> type = parameter.getType();
      Object value = args[i];
      Object oldValue = value;
      String argName = String.format("%s(%s)", methodName, parameter.getName());

      //使用方法上标注的清洗器
      for (CleanerInfo methodCleanerInfo : methodCleanerInfos) {
        value = clean(argName, value, type, methodCleanerInfo, true);
      }


      //使用属性自有清洗器
      for (CleanerInfo cleanerInfo : getCleanerInfos(parameter.getDeclaredAnnotations())) {
        value = clean(argName, value, type, cleanerInfo, false);
      }

      //对属性进行清洗
      if (value != null) cleanFields(argName, value);

      if (value != oldValue) args[i] = value;
    }
  }

  /**
   * 从一个注解数组中挑选被@CleanerAnnotation标注的元素。
   *
   * @param annotations 注解数组
   * @return 返回清洗器信息
   */
  public static CleanerInfo[] getCleanerInfos(Annotation... annotations) {
    return Arrays.stream(annotations).map(it -> {
      CleanerAnnotation cleanerAnnotation = it.annotationType().getAnnotation(CleanerAnnotation.class);
      if (cleanerAnnotation == null) return null;
      return new CleanerInfo(cleanerAnnotation, it, cleanerAnnotation.value());
    }).filter(Objects::nonNull).toArray(CleanerInfo[]::new);
  }

  /**
   * 获取可处理的数据类型
   *
   * @param cleanerClass 清洗器类
   * @return 返回可处理数据的类型, 如果不可处理, 返回null
   */
  @Nullable
  public static Class<?> getDataClass(Class<? extends Cleaner> cleanerClass) {
    Type[] types = ReflectUtils.getGenericsBySuper(cleanerClass, Cleaner.class);
    if (types.length == 0) return null;
    return (Class<?>) types[0];
  }

  /**
   * 获取可接收的注解类型
   *
   * @param cleanerClass 清洗器类
   * @return 返回可接收的注解类型, 如果没有, 返回null
   */
  @Nullable
  public static Class<?> getAnnotationClass(Class<? extends Cleaner> cleanerClass) {
    Type[] types = ReflectUtils.getGenericsBySuper(cleanerClass, Cleaner.class);
    if (types.length < 2) return null;
    return (Class<?>) types[1];
  }
}
